package org.apollo.swing.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class IOUtil
{
    /**
     * 缓冲区大小1MB
     */
    private static final int BUFFER_SIZE = 1024 * 1024;

    /**
     * 将输入流输出到输出流
     * @param in 输入流
     * @param out 输出流
     * @param bufferSize 缓冲区大小
     * @throws IOException
     */
    public static void in2OutStream(InputStream in, OutputStream out, int bufferSize) throws IOException
    {
        byte[] buffer = new byte[bufferSize];

        for(int bytesRead = 0; (bytesRead = in.read(buffer)) != -1;)
        {
            out.write(buffer, 0, bytesRead);
        }
    }

    /**
     * 读取文件返回字节数组流
     * @param file 文件
     * @return 字节数组流
     * @throws IOException
     */
    public static ByteArrayOutputStream readFileToByteStream(File file) throws IOException
    {
        FileInputStream fis = null;
        ByteArrayOutputStream bos = null;

        try
        {
            fis = new FileInputStream(file);
            bos = new ByteArrayOutputStream();
            in2OutStream(fis, bos, BUFFER_SIZE);
        }
        finally
        {
            if(fis != null)
            {
                fis.close();
            }
        }

        return bos;
    }

    /**
     * 读取文件返回字节数组
     * @param file 文件
     * @return 字节数组
     * @throws IOException
     */
    public static byte[] readFileToByteArray(File file) throws IOException
    {
        ByteArrayOutputStream bos = null;

        try
        {
            bos = readFileToByteStream(file);
        }
        finally
        {
            if(bos != null)
            {
                bos.close();
            }
        }

        return bos == null? null: bos.toByteArray();
    }
    
    /**
     * 使用默认字符集读取文件内容返回字符串
     * @param file 文件
     * @return String 文件内容
     * @throws IOException
     */
    public static String readFileToString(File file) throws IOException
    {
        return readFileToString(file, null);
    }

    /**
     * 使用指定字符集读取文件内容返回字符串
     * @param file 文件
     * @param charsetName 字符集名称
     * @return String 文件内容
     * @throws IOException
     */
    public static String readFileToString(File file, String charsetName) throws IOException
    {
        BufferedReader in = null;
        StringBuilder sb = new StringBuilder();
        
        try
        {
            if(charsetName == null)
            {
                in = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
            }
            else
            {
                in = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName));
            }
            
            char[] buffer = new char[BUFFER_SIZE];

            for(int bytesRead = 0; (bytesRead = in.read(buffer)) != -1;)
            {
                sb.append(Arrays.copyOf(buffer, bytesRead));
            }
        }
        finally
        {
            if(in != null)
            {
                in.close();
            }
        }
        
        return sb.toString();
    }
    
    /**
     * 使用默认字符集读取文件内容按行返回字符串集合
     * @param file 文件
     * @return List<String> 文件内容集合
     * @throws IOException
     */
    public static List<String> readFileToList(File file) throws IOException
    {
        return readFileToList(file, null);
    }
    
    /**
     * 使用指定字符集读取文件内容按行返回字符串集合
     * @param file 文件
     * @param charsetName 字符集名称
     * @return List<String> 文件内容集合
     * @throws IOException
     */
    public static List<String> readFileToList(File file, String charsetName) throws IOException
    {
        List<String> list = new ArrayList<String>();
        BufferedReader in = null;

        try
        {
            if(charsetName == null)
            {
                in = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
            }
            else
            {
                in = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName));
            }
            
            for(String line; (line = in.readLine()) != null;)
            {
                list.add(line);
            }
        }
        finally
        {
            if(in != null)
            {
                in.close();
            }
        }

        return list;
    }
    
    /**
     * 读取文件中从指定起始位置开始的字节
     * @param file 文件
     * @param start 起始位置
     * @return 字节数组
     * @throws IOException
     */
    public static byte[] readFileToByteArrayFrom(File file, long start) throws IOException
    {
        return readFileToByteArrayFrom(file, start, -1);
    }
    
    /**
     * 读取文件中从指定起始位置开始的指定长度的字节
     * @param file 文件
     * @param start 起始位置
     * @param length 长度
     * @return 字节数组
     * @throws IOException
     */
    public static byte[] readFileToByteArrayFrom(File file, long start, int length) throws IOException
    {
        RandomAccessFile raf = null;
        ByteArrayOutputStream bos = null;
        
        try
        {
            raf = new RandomAccessFile(file, "r");
            bos = new ByteArrayOutputStream();
            boolean all = length < 0;
            byte[] buffer = new byte[!all && length < BUFFER_SIZE? length: BUFFER_SIZE];
            int newLength = 0;
            raf.seek(start);
            
            for(int bytesRead = 0; (bytesRead = raf.read(buffer)) != -1;)
            {
                newLength += bytesRead;
                
                if(!all && newLength >= length)
                {
                    bytesRead -= (newLength - length);
                    bos.write(buffer, 0, bytesRead);
                    break;
                }
                else
                {
                    bos.write(buffer, 0, bytesRead);
                }
            }
        }
        finally
        {
            try
            {
                if(raf != null)
                {
                    raf.close();
                }
            }
            finally
            {
                if(bos != null)
                {
                    bos.close();
                }
            }
        }
        
        return bos == null? null: bos.toByteArray();
    }
    
    /**
     * 解析CSV文件
     * @param file 目标CSV文件
     * @return 解析结果：List<String[]>的每一个元素为一行，String[]的每一个元素为一个单元格
     * @throws IOException
     */
    public static List<String[]> parseCSVFile(File file) throws IOException
    {
        List<String> fileDatas = readFileToList(file);
        List<String[]> csvDatas = new ArrayList<String[]>();
        List<String> lineDatas = null;
        StringBuilder fragment = null;
        int index = 0;
        
        for(String line: fileDatas)
        {
            index = 0;
            
            if(fragment == null)
            {
                lineDatas = new ArrayList<String>();
            }
            
            for(String cell: line.split(",", -1))
            {
                if(fragment == null)
                {
                    //完整，无特殊字符
                    if(!cell.startsWith("\""))
                    {
                        lineDatas.add(cell.replaceAll("\"\"", "\""));
                    }
                    //完整，有特殊字符
                    else if(cell.endsWith("\"") && countLastDoubleQuotes(cell) % 2 == 1)
                    {
                        cell = cell.substring(1, cell.length() - 1);
                        lineDatas.add(cell.replaceAll("\"\"", "\""));
                    }
                    //不完整，有特殊字符
                    else
                    {
                        fragment = new StringBuilder(cell);
                    }
                }
                else
                {
                    fragment.append(index == 0? '\n': ',');
                    fragment.append(cell);
                    
                    //完整，有特殊字符
                    if(cell.endsWith("\"") && countLastDoubleQuotes(cell) % 2 == 1)
                    {
                        lineDatas.add(fragment.substring(1, fragment.length() - 1).replaceAll("\"\"", "\""));
                        fragment = null;
                    }
                }
                
                index++;
            }
            
            if(fragment != null)
            {
                continue;
            }
            else
            {
                csvDatas.add(lineDatas.toArray(new String[lineDatas.size()]));
            }
        }
        
        return csvDatas;
    }
    
    /**
     * 计算字符串末尾半角双引号的个数
     * @param str 目标字符串
     * @return 字符串末尾半角双引号的个数
     */
    private static int countLastDoubleQuotes(String str)
    {
        final char doubleQuotes = '\"';
        int count = 0;
        
        for(int i = str.length() - 1; i >= 0; i--)
        {
            if(str.charAt(i) == doubleQuotes)
            {
                count++;
            }
            else
            {
                break;
            }
        }
        
        return count;
    }

    /**
     * 复制文件
     * @param src 源文件
     * @param dest 目标文件
     * @param cover 是否覆盖
     * @throws IOException
     */
    public static void copyFile(File src, File dest, boolean cover) throws IOException
    {
        FileInputStream in = null;
        FileOutputStream out = null;

        try
        {
            if(!dest.exists())
            {
                dest.createNewFile();
            }
            else if(dest.exists() && cover)
            {
                dest.delete();
                dest.createNewFile();
            }
            else
            {
                throw new IOException("File " + dest + " already exists");
            }

            in = new FileInputStream(src);
            out = new FileOutputStream(dest);
            in2OutStream(in, out, BUFFER_SIZE);
        }
        finally
        {
            try
            {
                if(in != null)
                {
                    in.close();
                }
            }
            finally
            {
                if(out != null)
                {
                    out.close();
                }
            }
        }
    }
    
    /**
     * 将文本内容写入文件，不支持插入
     * @param file 文件
     * @param content 文本内容
     * @throws IOException
     */
    public static void writeToFile(File file, String content) throws IOException
    {
        writeToFile(file, content, null, false);
    }

    /**
     * 使用指定字符集将文本内容写入文件
     * @param file 文件
     * @param content 文本内容
     * @param charsetName 字符集名称
     * @param append 是否追加到文件末尾
     * @throws IOException
     */
    public static void writeToFile(File file, String content, String charsetName, boolean append) throws IOException
    {
        PrintWriter out = null;
        BufferedReader in = null;

        try
        {
            if(!file.exists())
            {
                file.createNewFile();
            }
            
            char[] buffer = new char[BUFFER_SIZE];
            in = new BufferedReader(new StringReader(content));
            
            if(charsetName == null)
            {
                out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, append))));
            }
            else
            {
                out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, append), charsetName)));
            }

            for(int bytesRead = 0; (bytesRead = in.read(buffer)) != -1;)
            {
                out.write(buffer, 0, bytesRead);
            }
        }
        finally
        {
            try
            {
                if(in != null)
                {
                    in.close();
                }
            }
            finally
            {
                if(out != null)
                {
                    out.close();
                }
            }
        }
    }
    
    /**
     * 将字节码数组写入文件
     * @param file 文件
     * @param content 字节码数组
     * @param append 是否追加到文件末尾
     * @throws IOException
     */
    public static void writeToFile(File file, byte[] content, boolean append) throws IOException
    {
        ByteArrayInputStream in = null;
        FileOutputStream out = null;
        
        try
        {
            if(!file.exists())
            {
                file.createNewFile();
            }
            
            in = new ByteArrayInputStream(content);
            out = new FileOutputStream(file, append);
            in2OutStream(in, out, BUFFER_SIZE);
        }
        finally
        {
            try
            {
                if(in != null)
                {
                    in.close();
                }
            }
            finally
            {
                if(out != null)
                {
                    out.close();
                }
            }
        }
    }
    
    /**
     * 在文件指定位置写入字节，会覆盖原来的内容
     * @param file 文件
     * @param content 字节数组
     * @param pos 要插入的位置
     * @throws IOException
     */
    public static void writeToFileAt(File file, byte[] content, long pos) throws IOException
    {
        RandomAccessFile raf = null;
        
        try
        {
            raf = new RandomAccessFile(file, "rw");
            raf.seek(pos);
            raf.write(content);
        }
        finally
        {
            try
            {
                if(raf != null)
                {
                    raf.close();
                }
            }
            finally
            {}
        }
    }
    
    /**
     * 在文件指定位置插入字节
     * @param file 文件
     * @param content 字节数组
     * @param pos 要插入的位置
     * @throws IOException
     */
    public static void insertToFileAt(File file, byte[] content, long pos) throws IOException
    {
        long length = file.length();
        
        if(pos >= length)
        {
            writeToFileAt(file, content, pos);
        }
        else
        {
            RandomAccessFile raf = null;
            long firstLength = length;
            long lastLength = firstLength - pos;
            int insertLength = content.length;
            byte[] buffer = new byte[BUFFER_SIZE];
            
            try
            {
                raf = new RandomAccessFile(file, "rw");
                int bytesRead;
                
                while(lastLength > 0)
                {
                    if(lastLength < BUFFER_SIZE)
                    {
                        buffer = new byte[(int)lastLength];
                    }
                    
                    raf.seek(firstLength - buffer.length);
                    bytesRead = raf.read(buffer);
                    firstLength -= bytesRead;
                    lastLength = firstLength - pos;
                    raf.seek(firstLength + insertLength);
                    raf.write(Arrays.copyOf(buffer, bytesRead));
                }
                
                raf.seek(pos);
                raf.write(content);
            }
            finally
            {
                try
                {
                    if(raf != null)
                    {
                        raf.close();
                    }
                }
                finally
                {}
            }
        }
    }
    
    /**
     * 删除文件指定位置开始的一个或多个字节
     * @param file 文件
     * @param pos 要删除的位置
     * @param length 要删除的长度
     * @throws IOException
     */
    public static void removeFromFileAt(File file, long pos, int length) throws IOException
    {
        final long fileLength = file.length();
        RandomAccessFile raf = null;
        final int BUFFER_SIZE = 10;
        
        try
        {
            raf = new RandomAccessFile(file, "rw");
            
            if(fileLength <= pos + length)
            {
                raf.setLength(pos);
            }
            else
            {
                boolean hasNext = true;
                long pointer = pos + length;
                long lastLength = fileLength - pointer;
                byte[] buffer = new byte[BUFFER_SIZE];
                int bytesRead;

                while(hasNext)
                {
                    if(lastLength <= BUFFER_SIZE)
                    {
                        buffer = new byte[(int)lastLength];
                        hasNext = false;
                    }
                    
                    raf.seek(pointer);
                    bytesRead = raf.read(buffer);
                    raf.seek(pointer - length);
                    raf.write(buffer);
                    pointer += bytesRead;
                    lastLength = fileLength - pointer;
                }
                
                raf.setLength(fileLength - length);
            }
        }
        finally
        {
            try
            {
                if(raf != null)
                {
                    raf.close();
                }
            }
            finally
            {}
        }
    }
    
    /**
     * 删除文件指定位置后的所有字节
     * @param file 文件
     * @param pos 要删除的位置
     * @throws IOException
     */
    public static void removeFromFileAt(File file, long pos) throws IOException
    {
        if(pos >= 0 && pos < file.length())
        {
            RandomAccessFile raf = null;

            try
            {
                raf = new RandomAccessFile(file, "rw");
                raf.setLength(pos);
            }
            finally
            {
                try
                {
                    if(raf != null)
                    {
                        raf.close();
                    }
                }
                finally
                {}
            }
        }
    }

    /**
     * 从控制台读取一串字符串
     * @return 读取的字符串
     * @throws IOException
     */
    public static String readStringFromSystemIn() throws IOException
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        try
        {
            return br.readLine();
        }
        finally
        {
            if(br != null)
            {
                br.close();
            }
        }
    }

    /**
     * 当ObjectInputStream对象调用readObject();时，会从ByteArrayInputStream流中反序列化输出的对象
     * @param bis 字节数组流
     * @return 对象输入流
     * @throws IOException
     */
    public static ObjectInputStream buildObjectInputStream(ByteArrayInputStream bis) throws IOException
    {
        return new ObjectInputStream(bis);
    }

    /**
     * 当ObjectOutputStream对象调用writeObject(o);时，o对象会序列化到ByteArrayOutputStream流中去
     * @param bos 字节数组流
     * @return 对象输出流
     * @throws IOException
     */
    public static ObjectOutputStream buildObjectOutputStream(ByteArrayOutputStream bos) throws IOException
    {
        return new ObjectOutputStream(bos);
    }

    /**
     * 创建String的BufferedReader
     * @param str String
     * @return BufferedReader
     */
    public static BufferedReader buildBufferedReader(String str)
    {
        return new BufferedReader(new StringReader(str));
    }

    /**
     * 创建String的ByteArrayInputStream
     * @param str String
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream buildByteArrayInputStream(String str)
    {
        return new ByteArrayInputStream(str.getBytes());
    }

    /**
     * 创建byte数组的ByteArrayInputStream
     * @param bytes byte数组
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream buildByteArrayInputStream(byte[] bytes)
    {
        return new ByteArrayInputStream(bytes);
    }
}